]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
notifications: Add header like in the new designs
authorJulian Sparber <julian@sparber.net>
Mon, 30 Oct 2023 15:03:47 +0000 (16:03 +0100)
committerMarge Bot <marge-bot@gnome.org>
Tue, 30 Jan 2024 17:05:23 +0000 (17:05 +0000)
See:
https://gitlab.gnome.org/Teams/Design/os-mockups/-/blob/9e2bed6f37030dbf7d82087623f266d3c012c949/notifications-calendar/notifications-grouping.png

This also increases the size of the icon to 48px.

Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6743
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3103>

data/theme/gnome-shell-sass/widgets/_message-list.scss
js/ui/calendar.js
js/ui/messageList.js
js/ui/mpris.js

index 15464bd80761e1bf8b9b9362879ac870e6dc076d..10fa88258767a85bd11485a88aad1103d0e1628e 100644 (file)
   @extend %card;
   padding: 0 !important;
 
-  // icon container
-  .message-icon-bin {
-    padding: ($base_padding * 3);
-
-    &:ltr {padding-right:$base_padding;}
-    &:rtl {padding-left:$base_padding;}
+  // subtract side padding to accommodate the close button
+  &:ltr { padding-right: $base_padding - 2px; };
+  &:rtl { padding-left: $base_padding - 2px; };
+
+  // message header
+  .message-header {
+    padding: 0 $scaled_padding;
+    margin: 0 $base_padding;
+    margin-top: $base_padding;
+    color: $insensitive_fg_color;
+    spacing: $base_padding;
 
-    // icon size and color
-    > StIcon {
-      icon-size: $large_icon_size;
+    // header source icon
+    .message-source-icon {
+      padding: 2px 0;
+      icon-size: $scalable_icon_size; // 16px
       -st-icon-style: symbolic;
     }
-  }
 
-  // content
-  .message-content {
-    spacing: 4px;
-    padding: ($base_padding * 1.5);
-    margin-bottom: $base_margin * 2;
+    // box that contains the source icon, source name and timestamp of the message
+    .message-header-content {
+      spacing: $base_padding;
+      min-height: to_em(24px);
+      padding-bottom: $base_padding;
+
+      // header source title
+      .message-source-title {
+        font-weight: bold;
+      }
+
+      // Time label
+      .event-time {
+        @extend %caption;
+        color: $insensitive_fg_color;
+        // Add bottom padding to align the app name with the time horizontally
+        padding-bottom: to_em(1px);
+
+        &:ltr { text-align: right };
+        &:rtl { text-align: left };
+      }
+    }
+
+    // close button
+    .message-close-button {
+      @extend .icon-button;
+      color: $fg_color !important;
+      background-color: transparentize($fg_color, 0.8);
+      padding: 0 !important;
+      border: 4px transparent solid;
+      &:hover {background-color: transparentize($fg_color, 0.7);}
+      &:insensitive {background-color: transparentize($fg_color, 0.9);}
+      &:active {background-color: transparentize($fg_color, 0.8);}
+    }
   }
 
-  .message-title-box {
+  // container for message contents
+  .message-box {
+    padding: 0 $base_padding;
+    padding-bottom: $base_padding;
+    margin: $base_padding;
+    margin-top: $base_padding;
     spacing: $base_padding;
-    // title
-    .message-title {
-      font-weight: bold;
-    }
 
-    // notification time stamp
-    > .event-time {
-      @extend %caption;
-      color: $insensitive_fg_color;
-      padding-bottom: to_em(1px);
+    // icon container
+    .message-icon-bin {
+      &:ltr {padding-right:$base_padding;}
+      &:rtl {padding-left:$base_padding;}
 
-      &:ltr { text-align: right };
-      &:rtl { text-align: left };
+      // icon size and color
+      > StIcon {
+        icon-size: $base_icon_size * 3; // 48px
+        -st-icon-style: symbolic;
+      }
     }
-  }
 
+    // If the header isn't displayed we need more top margin
+    &:first-child {
+      margin-top: $base_padding * 2;
+    }
 
-  // close button
-  .message-close-button {
-    @extend .icon-button;
-    color: $fg_color;
-    background-color: transparentize($fg_color, 0.8);
-    padding: 0 !important;
-    border: 5px transparent solid;
-    margin: 1px;
-    &:hover {background-color: transparentize($fg_color, 0.7);}
-    &:active {background-color: transparentize($fg_color, 0.8);}
-  }
+    // text of the message
+    .message-content {
+      spacing: $base_margin;
 
-  // body
-  .message-body {color: darken($fg_color, 10%);}
+      // message title
+      .message-title {
+        font-weight: bold;
+      }
+    }
+  }
 }
 
 // URLs in messages
 /* Media Controls */
 .message-media-control {
   padding: 0 $base_padding * 3;
-  margin: $base_padding * 2 0;
   border-radius: $base_border_radius;
   color: $fg_color;
   border: 1px solid transparent;
     }
   }
 
-  // fix margin for last button
-  &:last-child:ltr { margin-right: $base_margin * 3; }
-  &:last-child:rtl { margin-left: $base_margin * 3; }
-
   & StIcon { icon-size: $base_icon_size; }
 }
 
index fc4ebd1b9be5170b67946ad098a6c5451c01b062..455cfa1847431074e384922b4fcfdcdd585b6a50 100644 (file)
@@ -767,7 +767,7 @@ export const Calendar = GObject.registerClass({
 export const NotificationMessage = GObject.registerClass(
 class NotificationMessage extends MessageList.Message {
     _init(notification) {
-        super._init(notification.title, notification.bannerBodyText);
+        super._init(notification.source, notification.title, notification.bannerBodyText);
         this.setUseBodyMarkup(notification.bannerBodyMarkup);
 
         this.notification = notification;
index 2ecea49652a51441fbf3f9424afd802c0e52d224..8338e77660b3947536f285c4b501ff5674fe1758 100644 (file)
@@ -375,6 +375,67 @@ class TimeLabel extends St.Label {
     }
 });
 
+const MessageHeader = GObject.registerClass(
+class MessageHeader extends St.BoxLayout {
+    constructor(source) {
+        super({
+            style_class: 'message-header',
+            x_expand: true,
+        });
+
+        const sourceIconEffect = new Clutter.DesaturateEffect();
+        const sourceIcon = new St.Icon({
+            style_class: 'message-source-icon',
+            y_align: Clutter.ActorAlign.CENTER,
+            fallback_icon_name: 'application-x-executable-symbolic',
+        });
+        sourceIcon.add_effect(sourceIconEffect);
+        this.add_child(sourceIcon);
+
+        sourceIcon.connect('style-changed', () => {
+            const themeNode = sourceIcon.get_theme_node();
+            sourceIconEffect.enabled = themeNode.get_icon_style() === St.IconStyle.SYMBOLIC;
+        });
+
+        const headerContent = new St.BoxLayout({
+            style_class: 'message-header-content',
+            y_align: Clutter.ActorAlign.CENTER,
+            x_expand: true,
+        });
+        this.add_child(headerContent);
+
+        this.closeButton = new St.Button({
+            style_class: 'message-close-button',
+            icon_name: 'window-close-symbolic',
+            y_align: Clutter.ActorAlign.CENTER,
+            opacity: 0,
+        });
+        this.add_child(this.closeButton);
+
+        const sourceTitle = new St.Label({
+            style_class: 'message-source-title',
+            y_align: Clutter.ActorAlign.END,
+        });
+        headerContent.add_child(sourceTitle);
+
+        source.bind_property_full('title',
+            sourceTitle,
+            'text',
+            GObject.BindingFlags.SYNC_CREATE,
+            // Translators: this is the string displayed in the header when a message
+            // source doesn't have a name
+            (bind, value) => [true, value === null || value === '' ? _('Unknown App') : value],
+            null);
+        source.bind_property('icon',
+            sourceIcon,
+            'gicon',
+            GObject.BindingFlags.SYNC_CREATE);
+
+        this.timeLabel = new TimeLabel();
+        headerContent.add_child(this.timeLabel);
+    }
+});
+
 export const Message = GObject.registerClass({
     Signals: {
         'close': {},
@@ -382,7 +443,7 @@ export const Message = GObject.registerClass({
         'unexpanded': {},
     },
 }, class Message extends St.Button {
-    _init(title, body) {
+    _init(source, title, body) {
         super._init({
             style_class: 'message',
             accessible_role: Atk.Role.NOTIFICATION,
@@ -400,7 +461,12 @@ export const Message = GObject.registerClass({
         });
         this.set_child(vbox);
 
-        let hbox = new St.BoxLayout();
+        this._header = new MessageHeader(source);
+        vbox.add_child(this._header);
+
+        const hbox = new St.BoxLayout({
+            style_class: 'message-box',
+        });
         vbox.add_child(hbox);
 
         this._actionBin = new St.Widget({
@@ -427,26 +493,12 @@ export const Message = GObject.registerClass({
         this._mediaControls = new St.BoxLayout();
         hbox.add_child(this._mediaControls);
 
-        let titleBox = new St.BoxLayout({style_class: 'message-title-box'});
-        contentBox.add_child(titleBox);
-
         this.titleLabel = new St.Label({
             style_class: 'message-title',
             y_align: Clutter.ActorAlign.END,
         });
         this.setTitle(title);
-        titleBox.add_child(this.titleLabel);
-
-        this._timeLabel = new TimeLabel();
-        titleBox.add_child(this._timeLabel);
-
-        this._closeButton = new St.Button({
-            style_class: 'message-close-button',
-            icon_name: 'window-close-symbolic',
-            y_align: Clutter.ActorAlign.CENTER,
-            opacity: 0,
-        });
-        titleBox.add_child(this._closeButton);
+        contentBox.add_child(this.titleLabel);
 
         this._bodyStack = new St.Widget({x_expand: true});
         this._bodyStack.layout_manager = new LabelExpanderLayout();
@@ -457,10 +509,11 @@ export const Message = GObject.registerClass({
         this._bodyStack.add_child(this.bodyLabel);
         this.setBody(body);
 
-        this._closeButton.connect('clicked', this.close.bind(this));
-        let actorHoverId = this.connect('notify::hover', this._sync.bind(this));
-        this._closeButton.connect('destroy', this.disconnect.bind(this, actorHoverId));
         this.connect('destroy', this._onDestroy.bind(this));
+
+        this._header.closeButton.connect('clicked', this.close.bind(this));
+        let actorHoverId = this.connect('notify::hover', this._sync.bind(this));
+        this._header.closeButton.connect('destroy', this.disconnect.bind(this, actorHoverId));
         this._sync();
     }
 
@@ -474,11 +527,11 @@ export const Message = GObject.registerClass({
     }
 
     get datetime() {
-        return this._timeLabel.datetime;
+        return this._header.timeLabel.datetime;
     }
 
     set datetime(datetime) {
-        this._timeLabel.datetime = datetime;
+        this._header.timeLabel.datetime = datetime;
     }
 
     setTitle(text) {
@@ -596,8 +649,8 @@ export const Message = GObject.registerClass({
 
     _sync() {
         let visible = this.hover && this.canClose();
-        this._closeButton.opacity = visible ? 255 : 0;
-        this._closeButton.reactive = visible;
+        this._header.closeButton.opacity = visible ? 255 : 0;
+        this._header.closeButton.reactive = visible;
     }
 
     _onDestroy() {
index bf73e0ef523bccb3d523dcb3dea3faba186be9f8..78b61f4cf9ddf6cf4ea12b0a48ef64ce81e94b20 100644 (file)
@@ -23,17 +23,13 @@ const MPRIS_PLAYER_PREFIX = 'org.mpris.MediaPlayer2.';
 export const MediaMessage = GObject.registerClass(
 class MediaMessage extends MessageList.Message {
     _init(player) {
-        super._init('', '');
+        super._init(player.source, '', '');
 
         this._player = player;
 
         this._icon = new St.Icon({style_class: 'media-message-cover-icon'});
         this.setIcon(this._icon);
 
-        // reclaim space used by unused elements
-        this._timeLabel.hide();
-        this._closeButton.hide();
-
         this._prevButton = this.addMediaControl('media-skip-backward-symbolic',
             () => {
                 this._player.previous();
@@ -104,6 +100,7 @@ export class MprisPlayer extends Signals.EventEmitter {
         this._trackTitle = '';
         this._trackCoverUrl = '';
         this._busName = busName;
+        this.source = new MessageList.Source();
     }
 
     get status() {
@@ -229,6 +226,11 @@ export class MprisPlayer extends Signals.EventEmitter {
             this._app = null;
         }
 
+        this.source.set({
+            title: this._app?.get_name() ?? null,
+            icon: this._app?.get_icon() ?? null,
+        });
+
         this.emit('changed');
 
         let visible = this._playerProxy.CanPlay;